/*
 *  ChipmunkPhys.m
 *  pogostick
 *
 * The C functions below are the Chipmunk Physics callbacks...
 *
 *  Created by daddy (chuck@kollar.com) on 10/27/09.
 *  Copyright 2009 Charles P. Kollar All rights reserved.
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#define __CHIPMUNK_PHYS_H__
#import "ChipmunkPhys.h"


// This is defined in GameLayer.m...
extern int currentScore;

// These are defined in GameLayer.m. They need to be attached to a 'Layer'.
// This object isn't a layer, it's just a place to hold objects that exist
// in a level.
extern ParticleSystem *emitter;
//extern ParticleSystem *thrustEmitter;

// set in menu.m by the user from yes/no buttons...
extern int soundp, musicp;


/*
 * When the amulet is found you get more points and bullets hurt less
 */
extern BOOL amuletFound;

int addPoints (int i) {
    return (amuletFound == YES) ? i*3 : i;
}
int subPoints (int i) {
    return (amuletFound == YES) ? i/2 : i;
}


////
//// The C functions below are the Chipmunk Physics callbacks...
////


// Iniatially this game had all of the actors hard coded. When I moved to
// a more object oriented approach (for reading levels in from svg files)
// I ran into a problem where sometimes in these callbacks from C code you
// want to do something to a particular Object C instance, but don't have
// a pointer to it.

// When the bullets hit someone, or just fall on the floor they are removed from the
// game. This happens in a Chipmunk callback, and during callbacks you are not supposed
// to delete any shapes. So we put them here for them to be removed later...
NSMutableArray *bulletsToDelete = nil;



// The pogostick foot hits the head of the stick figure that is shooting.
int footHitHeadEvent (cpArbiter *arb, cpSpace *space __attribute__ ((unused)), void *data __attribute__ ((unused))) {
    cpShape *foot, *head;
    cpArbiterGetShapes(arb, &foot, &head);
    
    // For the shape for the head, then in the 'init' method for person we put
    // the address of the Person instance in the data member of the cpShape structure.
    // We test both shapes since we are not sure which one is the person's head....
    id p = head->data;
    if (p != nil && [p isMemberOfClass: [Person class]] == YES) {
        if ([p shootingHand] != 0) [p die];
        else return 0; // already dead
    }
    
    [emitter setPosition: ccp(arb->contacts->p.x, arb->contacts->p.y)];
    [emitter setTotalParticles: 20.0f];
    
    currentScore += addPoints(POINTS_FOR_PERSON_KILL);
    
    // The object is dead, don't process the collision further
    return 0;
}

// Note that the amulet obect itself is not destroyed until the next running of
// -(void) step: (ccTime) delta in GameLayer.m Chipmunk does not like you removing
// shapes or bodies while in it's cpSpaceStep routine.
int footHitBugEvent (cpArbiter *arb, cpSpace *space __attribute__ ((unused)), void *data __attribute__ ((unused))) {
    // If it's been found before, we should not go through this again...
    extern BOOL amuletFound;
    if (amuletFound == YES) return 0;
    
    cpShape *foot, *bug;
    cpArbiterGetShapes(arb, &foot, &bug);
    
    amuletFound = YES;
    
    // This is the icon of an amulet that is in the "Overlay" layer in GameLayer.m
    // The sprite is created at game startup, but only made visible when the amulet
    // is found. This could be from game saved information where the user has found
    // it before...
    extern Sprite *amuletSprite;
    [amuletSprite setVisible: YES];
    
    [emitter setPosition: ccp(arb->contacts->p.x, arb->contacts->p.y)];
    [emitter setTotalParticles: 20.0f];
    
    // The object is dead, don't process the collision further
    return 0;
}

// collision handlers and post step callbacks together to remove an object when it touches another.
// remember that static objects must be treated differently...
static void
postStepRemoveStatic(cpSpace *space, cpShape *shape, void *data __attribute__ ((unused)))
{
    cpSpaceRemoveStaticShape(space, shape);
    cpSpaceRehashStatic(space);
}

static void
postStepRemove(cpSpace *space, cpShape *shape, void *data __attribute__ ((unused)))
{
    cpSpaceRemoveBody(space, shape->body);
    cpBodyFree(shape->body);
    
    cpSpaceRemoveShape(space, shape);
    cpShapeFree(shape);
}

// pogostick get's hit with bullet...
int bulletHitStickEvent (cpArbiter *arb, cpSpace *space, void *data __attribute__ ((unused))) {
    cpShape *bullet, *stick;
    cpArbiterGetShapes(arb, &bullet, &stick);
    
    // This position should track the stick and should be something other than the current emitter...
    [emitter setPosition: ccp(arb->contacts->p.x, arb->contacts->p.y)];
    [emitter setTotalParticles: 10.0f];
    
    currentScore -= subPoints(POINTS_BULLET_HIT);
    
    // Also mark the bullet for removal like with the pills that pop...
    // Add a post step callback to safely remove the object from the space
    // obj is used a key, you can only register one callback per unique value for obj
    cpSpaceAddPostStepCallback(space, (cpPostStepFunc)postStepRemove, bullet, NULL);
    
    // The object is dead, don't process the collision further
    return 0;
}

// For then the bullet misses the pogostick and falls to the ground...
int bulletHitGroundEvent (cpArbiter *arb, cpSpace *space, void *data __attribute__ ((unused))) {
    // Get the cpShapes involved in the collision
    // The order will be the same as you defined in the handler definition
    // a->collision_type will be BULLET_TYPE and b->collision_type will be MONSTER_TYPE
    cpShape *bullet, *ground;
    cpArbiterGetShapes(arb, &bullet, &ground);
    
    //NSLog(@"bulletHitGroundEvent cpShape = 0x%x", bullet);
    // don't litter...
    // Add a post step callback to safely remove the object from the space
    cpSpaceAddPostStepCallback(space, (cpPostStepFunc)postStepRemove, bullet, NULL);
    
    // The object is dead, don't process the collision further
    return 0;
}

// Here we determine if the data stored in the shape is a 'PillCage' and if it is
// we remove the 'ShapeWrapper' that contains that 'cpShape' from the 'pills' NSMutableArray
// found in the 'PillCage'.
BOOL removeShapeWrapperForShapeInPillCage (cpShape *x) {
    id pc = x->data;
    if (pc != nil && [pc isMemberOfClass: [PillCage class]] == YES) {
        NSEnumerator *e = [[pc pills] objectEnumerator];
        ShapeWrapper *sw;
        while (sw = [e nextObject]) {
            // find the wrapper that contains this cpShape...
            if ([sw shape] == x) {
                [[pc pills] removeObjectIdenticalTo: sw];
                return YES;
            }
        }
    }
    return NO;
}

// These are the power up pills from the cages that were released when the sick
// jumped on top of the cage. These are handled differently from the power up pills
// that are attached to the static space.
int pill2HitStick (cpArbiter *arb, cpSpace *space, void *data __attribute__ ((unused))) {
    cpShape *pill, *stick;
    cpArbiterGetShapes(arb, &pill, &stick);
    
    // if we can't remove this pill from the cage from whence it came, disgard the collision...
    if (!removeShapeWrapperForShapeInPillCage(pill) == YES) return 0;
    
    currentScore += addPoints(POINTS_HITTING_FREE_PILL);
    [emitter setPosition: ccp(arb->contacts->p.x, arb->contacts->p.y)];
    [emitter setTotalParticles: 6.0f];
    
    if (soundp != 0) {
        [[SimpleAudioEngine sharedEngine] playEffect: STICK_HIT_PILL_WAV];
    }
    
    // Add a post step callback to safely remove the object from the space
    cpSpaceAddPostStepCallback(space, (cpPostStepFunc)postStepRemove, pill, NULL);
    
    // The object is dead, don't process the collision further
    return 0;
}

// It seems that it is not wise to remove shapes from inside of a callback.
// So they need to be placed on a list and removed later...
// http://www.slembcke.net/forums/viewtopic.php?f=1&t=713

// These are the "power up pills" that are fixed to the [static] space above the floor.
int stickHitPillEvent (cpArbiter *arb, cpSpace *space, void *data) {
    cpShape *pill, *stick;
    cpArbiterGetShapes(arb, &pill, &stick);
    
    currentScore += addPoints(POINTS_HITTING_FIXED_PILL);
    [emitter setPosition: ccp(arb->contacts->p.x, arb->contacts->p.y)];
    [emitter setTotalParticles: 3.0f];
    
    // http://www.a1freesoundeffects.com/button.html
    if (soundp != 0) {
        [[SimpleAudioEngine sharedEngine] playEffect: STICK_HIT_PILL_WAV];
    }
    
    // Add a post step callback to safely remove the object from the space
    cpSpaceAddPostStepCallback(space, (cpPostStepFunc)postStepRemoveStatic, pill, NULL);
    
	// Returning 0 will cause the collision to be discarded. This allows you to do conditional collisions.
	return 0;
}


// First the foot hits the floor, then the foot hits the stick.
// This is done so that a state machine can be setup to allow the impulse
// to only occur after the foot hits the floor, and then after it hits the stick.
int footHitFloorEvent (cpArbiter *arb, cpSpace *space __attribute__ ((unused)), void *data __attribute__ ((unused))) {
    cpShape *foot, *floor;
    cpArbiterGetShapes(arb, &foot, &floor);
    
	Level *me = (Level *)data;
    PogoStick *stick = [me stick];
    cpBody *footBody = [stick footBody];
    
    //NSLog(@"!!!!!Foot hit Floor!!!\n");
    
    [stick setFootTouchedFloorCount: 3];

    // stop all movement on the foot....
    cpBodyResetForces(footBody);
    footBody->v = cpvzero; footBody->w = 0.0f;
    
    return 1;
}

/*
 * Open the doors and let the pills out but first we need to determine which PillCage instance this event
 * belongs too. Also, since the top of the pill cage is sort of a floor, this callback also does the things
 * that 'footHitFloorEvent' would do in terms of marking that event...
 */
int footHitReleasePillEvent (cpArbiter *arb, cpSpace *space __attribute__ ((unused)), void *data) {
	Level *me = (Level *)data;
    PogoStick *stick = [me stick];

	//NSLog(@"Stick hit PillCage release door!!!\n");
    
    BOOL foundShape = NO;
    // Look through the array of PillCages on this level to determine which one caused this callback...
    NSEnumerator *enumerator = [[me pillCages] objectEnumerator];
    PillCage *pc;
    // Look through the PillCages that have been created in the Level instance for one whose triggerShape
    // matches one of the shapes that caused this event.
    while (pc = [enumerator nextObject]) {
        cpCollisionType pillCageCt = [pc collisionType];
        if (pillCageCt < PILLCAGE_STARTING_COLISSION_TYPE_VALUE) {
            NSLog(@"ERROR... footHitReleasePillEvent... found cpCollisionType < PILLCAGE_STARTING_COLISSION_TYPE_VALUE??");
        }
        if (arb->a->collision_type == pillCageCt || arb->b->collision_type == pillCageCt) {
            foundShape = YES;
            if ([pc hasBeenOpened] == YES) break;
                
            // swing the door out...
            ((cpGearJoint *)[pc doorHinge])->phase = ([pc facingLeft] == YES ? 1 : -1) * M_PI/2;
            
            if (soundp != 0) {
                [[SimpleAudioEngine sharedEngine] playEffect:FOOT_HIT_RELEASE_WAV];
            } 
            
            // after opening the door (changing the phase of the gearjoint above)
            // we add a little "extra" zing to the pills (boust the 'shape->e')
            // to make them jump around when they hit the ground...
            enumerator = [[pc pills] objectEnumerator];
            NSString *element;
            while (element = [enumerator nextObject]) {
                ShapeWrapper *shapeWrapper = (ShapeWrapper *)element;
                [shapeWrapper shape]->e = 1.4f;
            }
            
            [pc setHasBeenOpened: YES];
            break;
        }
    }
    // This is actually a rather serious bug. We will make a note of it in the log and keep going...
    if (foundShape == NO) {
        NSLog(@"Unable to locate trigger shape in PillCage for 'footHitReleasePillEvent'?");
    }
    
    [stick jump];
    
	// Returning 0 will cause the collision to be discarded. This allows you to do conditional collisions.
	return 1;
}

